// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package httputil

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// drainBody将所有b读入内存，然后返回两个等效的
// ReadCloser，产生相同的字节。
// 
// 如果所有字节的初始发音都失败，则返回一个错误。它不会试图使返回的ReadCloser具有相同的错误匹配行为。
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
	if b == nil || b == http.NoBody {
		// 无需复制。保留无人的魔力哨兵意义。
		return http.NoBody, http.NoBody, nil
	}
	var buf bytes.Buffer
	if _, err = buf.ReadFrom(b); err != nil {
		return nil, b, err
	}
	if err = b.Close(); err != nil {
		return nil, b, err
	}
	return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil
}

// dumpConn是一个网络。康涅狄格州写信给作者并从读者那里读到
type dumpConn struct {
	io.Writer
	io.Reader
}

func (c *dumpConn) Close() error                       { return nil }
func (c *dumpConn) LocalAddr() net.Addr                { return nil }
func (c *dumpConn) RemoteAddr() net.Addr               { return nil }
func (c *dumpConn) SetDeadline(t time.Time) error      { return nil }
func (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }
func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }

type neverEnding byte

func (b neverEnding) Read(p []byte) (n int, err error) {
	for i := range p {
		p[i] = byte(b)
	}
	return len(p), nil
}

// outGoingLength是未报告的
// （*http.Request）的副本。伸出长度法。
func outgoingLength(req *http.Request) int64 {
	if req.Body == nil || req.Body == http.NoBody {
		return 0
	}
	if req.ContentLength != 0 {
		return req.ContentLength
	}
	return -1
}

// DumpRequestOut类似于DumpRequest，但用于传出的客户端请求。它
// 包含标准http所允许的任何头。传输添加，例如
// 用户代理。
func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
	save := req.Body
	dummyBody := false
	if !body {
		contentLength := outgoingLength(req)
		if contentLength != 0 {
			req.Body = io.NopCloser(io.LimitReader(neverEnding('x'), contentLength))
			dummyBody = true
		}
	} else {
		var err error
		save, req.Body, err = drainBody(req.Body)
		if err != nil {
			return nil, err
		}
	}

	// 由于我们使用实际的传输代码来编写请求，
	// 切换到http，这样传输就不会试图与我们的dumpConn及其字节进行SSL 
	// 协商。缓冲和管道。
	// 无论如何，https和http的有线格式是相同的。
	reqSend := req
	if req.URL.Scheme == "https" {
		reqSend = new(http.Request)
		*reqSend = *req
		reqSend.URL = new(url.URL)
		*reqSend.URL = *req.URL
		reqSend.URL.Scheme = "http"
	}

	// 使用实际的传输代码记录我们将在线路上发送的内容，但不使用TCP。使用带有
	// 返回假网络的自定义拨号程序的传输。等待
	// 以获取完整输入（并记录它），然后用虚拟响应来响应
	// 的连接。
	var buf bytes.Buffer // 记录输出
	pr, pw := io.Pipe()
	defer pr.Close()
	defer pw.Close()
	dr := &delegateReader{c: make(chan io.Reader)}

	t := &http.Transport{
		Dial: func(net, addr string) (net.Conn, error) {
			return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
		},
	}
	defer t.CloseIdleConnections()

	// 我们需要此通道来确保读卡器
	// 如果t.RoundTrip返回错误，goroutine将退出。
	// 见戈朗。org/issue/32571。
	quitReadCh := make(chan struct{})
	// 等待请求，然后用虚拟响应进行回复：
	go func() {
		req, err := http.ReadRequest(bufio.NewReader(pr))
		if err == nil {
			// 确保所有正文都已读取；否则我们将得到部分转储。
			io.Copy(io.Discard, req.Body)
			req.Body.Close()
		}
		select {
		case dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n"):
		case <-quitReadCh:
			// 确保授权读者。如果出现错误，读取不会永远阻塞。
			close(dr.c)
		}
	}()

	_, err := t.RoundTrip(reqSend)

	req.Body = save
	if err != nil {
		pw.Close()
		dr.err = err
		close(quitReadCh)
		return nil, err
	}
	dump := buf.Bytes()

	// 如果我们在上面使用了一个假人，现在就把它移除。
	// TODO:如果请求。ContentLength很大，我们不必要地分配内存
	// 只是在这里将其切掉。但这只是一个调试函数，所以现在可以接受。如果这很重要的话，我们可以早点丢弃尸体。
	if dummyBody {
		if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
			dump = dump[:i+4]
		}
	}
	return dump, nil
}

// delegateReader是一个一旦到达某个频道，就会委托给另一个阅读器的阅读器。
type delegateReader struct {
	c   chan io.Reader
	err error     // 仅当r为零且c关闭时使用。
	r   io.Reader // nil直到收到c 
}

func (r *delegateReader) Read(p []byte) (int, error) {
	if r.r == nil {
		var ok bool
		if r.r, ok = <-r.c; !ok {
			return 0, r.err
		}
	}
	return r.r.Read(p)
}

// 如果非空，则返回值，否则定义。
func valueOrDefault(value, def string) string {
	if value != "" {
		return value
	}
	return def
}

var reqWriteExcludeHeaderDump = map[string]bool{
	"Host":              true, // 无论如何不在头映射中
	"Transfer-Encoding": true,
	"Trailer":           true,
}

// DumpRequest在其HTTP/1中返回给定的请求。x线
// 表示。它应该只被服务器用来调试客户端
// 请求。返回的表示只是一个近似值；
// 将初始请求解析为http时，会丢失该请求的一些详细信息。要求特别是，标题字段
// 名称的顺序和大小写丢失。多值头中的值顺序保持
// 不变。HTTP/2请求被转储到HTTP/1中。x形式，而不是
// 原始二进制表示形式。
// 
// 如果body为true，DumpRequest也会返回body。为此，它
// 使用req。然后用新的io替换它。ReadCloser 
// 这会产生相同的字节。如果DumpRequest返回错误，
// req的状态未定义。
// 
// http的文档。要求写下转储中包含req的
// 字段的详细信息。
func DumpRequest(req *http.Request, body bool) ([]byte, error) {
	var err error
	save := req.Body
	if !body || req.Body == nil {
		req.Body = nil
	} else {
		save, req.Body, err = drainBody(req.Body)
		if err != nil {
			return nil, err
		}
	}

	var b bytes.Buffer

	// 默认情况下，打印出未修改的请求。RequestURI，
	// 始终为传入的服务器请求设置。但是因为我们
	// 以前使用过req。网址。RequestURI和文档不是
	// 始终非常清楚何时使用DumpRequest vs 
	// DumpRequestOut，如果调用方
	// 提供了非服务器请求，请回到原来的方式。
	reqURI := req.RequestURI
	if reqURI == "" {
		reqURI = req.URL.RequestURI()
	}

	fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
		reqURI, req.ProtoMajor, req.ProtoMinor)

	absRequestURI := strings.HasPrefix(req.RequestURI, "http:// /”|strings.HasPrefix（req.RequestURI，“https:
	if !absRequestURI {
		host := req.Host
		if host == "" && req.URL != nil {
			host = req.URL.Host
		}
		if host != "" {
			fmt.Fprintf(&b, "Host: %s\r\n", host)
		}
	}

	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
	if len(req.TransferEncoding) > 0 {
		fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
	}
	if req.Close {
		fmt.Fprintf(&b, "Connection: close\r\n")
	}

	err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
	if err != nil {
		return nil, err
	}

	io.WriteString(&b, "\r\n")

	if req.Body != nil {
		var dest io.Writer = &b
		if chunked {
			dest = NewChunkedWriter(dest)
		}
		_, err = io.Copy(dest, req.Body)
		if chunked {
			dest.(io.Closer).Close()
			io.WriteString(&b, "\r\n")
		}
	}

	req.Body = save
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}

// errNoBody是failureToReadBody使用的前哨错误值，因此我们可以检测到缺少body是故意的。
var errNoBody = errors.New("sentinel error value")

// failureToReadBody是一个io。ReadCloser，返回
// 读取。当我们实际上不想消耗
// 身体，但需要一个非零的，并且想要区分
// 读取虚拟体时出错。
type failureToReadBody struct{}

func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
func (failureToReadBody) Close() error             { return nil }

// emptyBody是empty reader的一个实例。
var emptyBody = io.NopCloser(strings.NewReader(""))

// DumpResponse类似于DumpRequest，但会转储响应。
func DumpResponse(resp *http.Response, body bool) ([]byte, error) {
	var b bytes.Buffer
	var err error
	save := resp.Body
	savecl := resp.ContentLength

	if !body {
		// 内容长度为零。确保正文是空的
		// 读取器，而不是通过failureToReadBody{}返回错误。
		if resp.ContentLength == 0 {
			resp.Body = emptyBody
		} else {
			resp.Body = failureToReadBody{}
		}
	} else if resp.Body == nil {
		resp.Body = emptyBody
	} else {
		save, resp.Body, err = drainBody(resp.Body)
		if err != nil {
			return nil, err
		}
	}
	err = resp.Write(&b)
	if err == errNoBody {
		err = nil
	}
	resp.Body = save
	resp.ContentLength = savecl
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}
